{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-block alert-danger\">\n",
    "Note: This notebook uses the python package lightfm which is not supported with Python 3.12!\n",
    "To run this notebook, use Python 3.11.\n",
    "</div>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Copyright © 2022 Gurobi Optimization, LLC\n",
    "\n",
    "# A Music Recommendation System With Mathematical Optimization "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A good song never gets old, except when it does. \n",
    "Music streaming services like Spotify periodically provide their millions of users with curated music recommendations to keep them wanting to come back for more. It is important that these recommendations truly resonate with their users, while also introducing them to novelty that keeps their curiosity alive.\n",
    "\n",
    "In this notebook, we will walk-through how to create a music recommendation system using a mixture of predictive and prescriptive analytics.\n",
    "The predictive component foresees what users might be into based on their past music preferences, while the prescriptive component uses these predictions to  create an optimally diverse recommendation list.\n",
    "\n",
    "**Goal**: Recommend new artists to users such that the artists are likeable and diverse.\n",
    "\n",
    "\n",
    "The datasets used in this notebook are pre-processed from two Kaggle datasets: \n",
    "- [Encrypted user data (Pichl, Zangerle and Specht, 2015)](https://www.kaggle.com/datasets/andrewmvd/spotify-playlists) with playlists created by [Spotify](https://open.spotify.com/) users, and \n",
    "- [Artist data](https://www.kaggle.com/datasets/pieca111/music-artists-popularity) with musician demographics collected from [Musicbrainz](https://musicbrainz.org/) and [Last.fm](last.fm).\n",
    "\n",
    "Pichl, Martin; Zangerle, Eva; Specht, Günther: \"Towards a Context-Aware Music Recommendation Approach: What is Hidden in the Playlist Name?\" in 15th IEEE International Conference on Data Mining Workshops (ICDM 2015), pp. 1360-1365, IEEE, Atlantic City, 2015.\n",
    "\n",
    "## Load the data\n",
    "\n",
    "To start, let's load the following packages for analyzing and visualizing the datasets."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "readonly": false,
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "%pip install gurobipy seaborn lightfm\n",
    "import numpy as np \n",
    "import pandas as pd \n",
    "import warnings \n",
    "warnings.filterwarnings(\"ignore\")\n",
    "from matplotlib import pyplot as plt \n",
    "import seaborn as sns"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We will first load the dataset with artist information. This dataset has 8,465 artists along with information on their  country of origin and the number of listeners they have on [Last.fm](https://www.last.fm/). Each artist is given an id from 0 through 8,465."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "readonly": false,
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "8465 artists\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>artist_id</th>\n",
       "      <th>artist</th>\n",
       "      <th>country</th>\n",
       "      <th>listeners</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>0</td>\n",
       "      <td>Coldplay</td>\n",
       "      <td>United Kingdom</td>\n",
       "      <td>5381567</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>1</td>\n",
       "      <td>Radiohead</td>\n",
       "      <td>United Kingdom</td>\n",
       "      <td>4732528</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>2</td>\n",
       "      <td>Red Hot Chili Peppers</td>\n",
       "      <td>United States</td>\n",
       "      <td>4620835</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>3</td>\n",
       "      <td>Rihanna</td>\n",
       "      <td>United States</td>\n",
       "      <td>4558193</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>4</td>\n",
       "      <td>Eminem</td>\n",
       "      <td>United States</td>\n",
       "      <td>4517997</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "   artist_id                 artist         country  listeners\n",
       "0          0               Coldplay  United Kingdom    5381567\n",
       "1          1              Radiohead  United Kingdom    4732528\n",
       "2          2  Red Hot Chili Peppers   United States    4620835\n",
       "3          3                Rihanna   United States    4558193\n",
       "4          4                 Eminem   United States    4517997"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "df_artist = pd.read_csv('https://raw.githubusercontent.com/Gurobi/modeling-examples/master/music_recommendation/artist_data.csv')\n",
    "print(len(df_artist),\"artists\")\n",
    "df_artist.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let us take a look at the distribution of the number of listeners for the most popular 2,000 artists."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "df_artist.set_index('artist')['listeners'].head(2000).plot(rot=40) \n",
    "plt.xlabel('Artist')\n",
    "plt.ylabel('Number of listeners (millions)') \n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Where are these artists from? Let us visualize the number of artists from each country. There are 102 countries in this dataset, and the three most common places of origin are: the United States, the United Kingdom and Sweden."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>country</th>\n",
       "      <th>count</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>United States</td>\n",
       "      <td>4041</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>United Kingdom</td>\n",
       "      <td>1500</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>Sweden</td>\n",
       "      <td>355</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>Spain</td>\n",
       "      <td>326</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>Canada</td>\n",
       "      <td>277</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>...</th>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>97</th>\n",
       "      <td>Saint Vincent and The Grenadines</td>\n",
       "      <td>1</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>98</th>\n",
       "      <td>Samoa</td>\n",
       "      <td>1</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>99</th>\n",
       "      <td>Guinea</td>\n",
       "      <td>1</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>100</th>\n",
       "      <td>Serbia</td>\n",
       "      <td>1</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>101</th>\n",
       "      <td>Algeria</td>\n",
       "      <td>1</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "<p>102 rows × 2 columns</p>\n",
       "</div>"
      ],
      "text/plain": [
       "                              country  count\n",
       "0                       United States   4041\n",
       "1                      United Kingdom   1500\n",
       "2                              Sweden    355\n",
       "3                               Spain    326\n",
       "4                              Canada    277\n",
       "..                                ...    ...\n",
       "97   Saint Vincent and The Grenadines      1\n",
       "98                              Samoa      1\n",
       "99                             Guinea      1\n",
       "100                            Serbia      1\n",
       "101                           Algeria      1\n",
       "\n",
       "[102 rows x 2 columns]"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "df_country = df_artist.groupby('country')['country'].count().sort_values(ascending = False).reset_index(name=\"count\")\n",
    "df_country.set_index('country')['count'].plot(rot=40) #Select countries with at least 100 artists from there\n",
    "plt.xlabel('Country')\n",
    "plt.ylabel('Number of artists originating from the country') \n",
    "plt.show() \n",
    "df_country"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Next, load the dataset with user information on playlists created by each of the 1,306 users. A playlist is a set of music tracks from various artists. This dataset is important to understand a user's preference over the artists. The number of tracks from an artist that appear in a user's playlist is a good measure of how much a user likes that artist."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1306 users\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>user_id</th>\n",
       "      <th>artist_id</th>\n",
       "      <th>artist</th>\n",
       "      <th>track</th>\n",
       "      <th>playlist</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>785</td>\n",
       "      <td>622</td>\n",
       "      <td>Elvis Costello</td>\n",
       "      <td>(The Angels Wanna Wear My) Red Shoes</td>\n",
       "      <td>HARD ROCK 2010</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>785</td>\n",
       "      <td>622</td>\n",
       "      <td>Elvis Costello</td>\n",
       "      <td>Alison</td>\n",
       "      <td>HARD ROCK 2010</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>785</td>\n",
       "      <td>622</td>\n",
       "      <td>Elvis Costello</td>\n",
       "      <td>Tramp The Dirt Down</td>\n",
       "      <td>HARD ROCK 2010</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>993</td>\n",
       "      <td>622</td>\n",
       "      <td>Elvis Costello</td>\n",
       "      <td>Don't Let Me Be Misunderstood</td>\n",
       "      <td>Everything at once</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>487</td>\n",
       "      <td>622</td>\n",
       "      <td>Elvis Costello</td>\n",
       "      <td>Alison</td>\n",
       "      <td>Home</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>5</th>\n",
       "      <td>300</td>\n",
       "      <td>622</td>\n",
       "      <td>Elvis Costello</td>\n",
       "      <td>Beautiful</td>\n",
       "      <td>Ángeles</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>6</th>\n",
       "      <td>155</td>\n",
       "      <td>622</td>\n",
       "      <td>Elvis Costello</td>\n",
       "      <td>Watching The Detectives - Single Version</td>\n",
       "      <td>Vergaarbaak</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>7</th>\n",
       "      <td>155</td>\n",
       "      <td>622</td>\n",
       "      <td>Elvis Costello</td>\n",
       "      <td>She</td>\n",
       "      <td>Voorbeschouwing</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>8</th>\n",
       "      <td>317</td>\n",
       "      <td>622</td>\n",
       "      <td>Elvis Costello</td>\n",
       "      <td>Deep Dark Truthful Mirror</td>\n",
       "      <td>We Are Hunted Paramore Chart Jan 12</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>9</th>\n",
       "      <td>697</td>\n",
       "      <td>622</td>\n",
       "      <td>Elvis Costello</td>\n",
       "      <td>Alison</td>\n",
       "      <td>All Time Great Songs</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>10</th>\n",
       "      <td>991</td>\n",
       "      <td>622</td>\n",
       "      <td>Elvis Costello</td>\n",
       "      <td>Veronica</td>\n",
       "      <td>WBER</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>11</th>\n",
       "      <td>469</td>\n",
       "      <td>622</td>\n",
       "      <td>Elvis Costello</td>\n",
       "      <td>She</td>\n",
       "      <td>Las tipicas canciones que no sabes como se lla...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>12</th>\n",
       "      <td>694</td>\n",
       "      <td>622</td>\n",
       "      <td>Elvis Costello</td>\n",
       "      <td>Waiting For The End Of The World</td>\n",
       "      <td>Starred</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>13</th>\n",
       "      <td>435</td>\n",
       "      <td>622</td>\n",
       "      <td>Elvis Costello</td>\n",
       "      <td>Alison</td>\n",
       "      <td>Liked from Radio</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>14</th>\n",
       "      <td>1184</td>\n",
       "      <td>622</td>\n",
       "      <td>Elvis Costello</td>\n",
       "      <td>Alison</td>\n",
       "      <td>best of radio paradise</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>15</th>\n",
       "      <td>1184</td>\n",
       "      <td>622</td>\n",
       "      <td>Elvis Costello</td>\n",
       "      <td>Watching The Detectives - Single Version</td>\n",
       "      <td>best of radio paradise</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>16</th>\n",
       "      <td>1204</td>\n",
       "      <td>622</td>\n",
       "      <td>Elvis Costello</td>\n",
       "      <td>(I Don't Want To Go To) Chelsea - 1977/Live At...</td>\n",
       "      <td>Starred</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>17</th>\n",
       "      <td>1204</td>\n",
       "      <td>622</td>\n",
       "      <td>Elvis Costello</td>\n",
       "      <td>(The Angels Wanna Wear My) Red Shoes</td>\n",
       "      <td>Starred</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>18</th>\n",
       "      <td>1204</td>\n",
       "      <td>622</td>\n",
       "      <td>Elvis Costello</td>\n",
       "      <td>(The Angels Wanna Wear My) Red Shoes - 1977/Li...</td>\n",
       "      <td>Starred</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>19</th>\n",
       "      <td>1204</td>\n",
       "      <td>622</td>\n",
       "      <td>Elvis Costello</td>\n",
       "      <td>(The Angels Wanna Wear My) Red Shoes - demo</td>\n",
       "      <td>Starred</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>20</th>\n",
       "      <td>1204</td>\n",
       "      <td>622</td>\n",
       "      <td>Elvis Costello</td>\n",
       "      <td>(What's So Funny 'Bout) Peace, Love And Unders...</td>\n",
       "      <td>Starred</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>21</th>\n",
       "      <td>1204</td>\n",
       "      <td>622</td>\n",
       "      <td>Elvis Costello</td>\n",
       "      <td>Alison</td>\n",
       "      <td>Starred</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>22</th>\n",
       "      <td>1204</td>\n",
       "      <td>622</td>\n",
       "      <td>Elvis Costello</td>\n",
       "      <td>Alison - 1977/Live At The Nashville Rooms</td>\n",
       "      <td>Starred</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>23</th>\n",
       "      <td>1204</td>\n",
       "      <td>622</td>\n",
       "      <td>Elvis Costello</td>\n",
       "      <td>Alison - 1977/Live At The Nashville Rooms - So...</td>\n",
       "      <td>Starred</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>24</th>\n",
       "      <td>1204</td>\n",
       "      <td>622</td>\n",
       "      <td>Elvis Costello</td>\n",
       "      <td>All Grown Up - Live</td>\n",
       "      <td>Starred</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>25</th>\n",
       "      <td>1204</td>\n",
       "      <td>622</td>\n",
       "      <td>Elvis Costello</td>\n",
       "      <td>Blame It On Cain</td>\n",
       "      <td>Starred</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>26</th>\n",
       "      <td>1204</td>\n",
       "      <td>622</td>\n",
       "      <td>Elvis Costello</td>\n",
       "      <td>Blame It On Cain - 1977/Live At The Nashville ...</td>\n",
       "      <td>Starred</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>27</th>\n",
       "      <td>1204</td>\n",
       "      <td>622</td>\n",
       "      <td>Elvis Costello</td>\n",
       "      <td>Blue Minute - Demo</td>\n",
       "      <td>Starred</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>28</th>\n",
       "      <td>1204</td>\n",
       "      <td>622</td>\n",
       "      <td>Elvis Costello</td>\n",
       "      <td>Brilliant Mistake</td>\n",
       "      <td>Starred</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>29</th>\n",
       "      <td>1204</td>\n",
       "      <td>622</td>\n",
       "      <td>Elvis Costello</td>\n",
       "      <td>Call On Me - Demo</td>\n",
       "      <td>Starred</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>30</th>\n",
       "      <td>1204</td>\n",
       "      <td>622</td>\n",
       "      <td>Elvis Costello</td>\n",
       "      <td>Changing Partners</td>\n",
       "      <td>Starred</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>31</th>\n",
       "      <td>1204</td>\n",
       "      <td>622</td>\n",
       "      <td>Elvis Costello</td>\n",
       "      <td>Complicated Shadows</td>\n",
       "      <td>Starred</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>32</th>\n",
       "      <td>1204</td>\n",
       "      <td>622</td>\n",
       "      <td>Elvis Costello</td>\n",
       "      <td>Crawling To The USA - 1977/Live At The Nashvil...</td>\n",
       "      <td>Starred</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>33</th>\n",
       "      <td>1204</td>\n",
       "      <td>622</td>\n",
       "      <td>Elvis Costello</td>\n",
       "      <td>Down Among the Wines and Spirits</td>\n",
       "      <td>Starred</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>34</th>\n",
       "      <td>1204</td>\n",
       "      <td>622</td>\n",
       "      <td>Elvis Costello</td>\n",
       "      <td>Everyday I Write The Book - Live</td>\n",
       "      <td>Starred</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>35</th>\n",
       "      <td>1204</td>\n",
       "      <td>622</td>\n",
       "      <td>Elvis Costello</td>\n",
       "      <td>God Give Me Strength</td>\n",
       "      <td>Starred</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>36</th>\n",
       "      <td>1204</td>\n",
       "      <td>622</td>\n",
       "      <td>Elvis Costello</td>\n",
       "      <td>God Give Me Strength - Live</td>\n",
       "      <td>Starred</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>37</th>\n",
       "      <td>1204</td>\n",
       "      <td>622</td>\n",
       "      <td>Elvis Costello</td>\n",
       "      <td>Heart Of The City - Live</td>\n",
       "      <td>Starred</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>38</th>\n",
       "      <td>1204</td>\n",
       "      <td>622</td>\n",
       "      <td>Elvis Costello</td>\n",
       "      <td>Hidden Shame</td>\n",
       "      <td>Starred</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>39</th>\n",
       "      <td>1204</td>\n",
       "      <td>622</td>\n",
       "      <td>Elvis Costello</td>\n",
       "      <td>Hoover Factory - 1977/Live At The Nashville Rooms</td>\n",
       "      <td>Starred</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>40</th>\n",
       "      <td>1204</td>\n",
       "      <td>622</td>\n",
       "      <td>Elvis Costello</td>\n",
       "      <td>How Deep Is the Red</td>\n",
       "      <td>Starred</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>41</th>\n",
       "      <td>1204</td>\n",
       "      <td>622</td>\n",
       "      <td>Elvis Costello</td>\n",
       "      <td>I Don't Want To Go Home - Demo</td>\n",
       "      <td>Starred</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>42</th>\n",
       "      <td>1204</td>\n",
       "      <td>622</td>\n",
       "      <td>Elvis Costello</td>\n",
       "      <td>I Dreamed of My Old Lover</td>\n",
       "      <td>Starred</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>43</th>\n",
       "      <td>1204</td>\n",
       "      <td>622</td>\n",
       "      <td>Elvis Costello</td>\n",
       "      <td>I Felt the Chill Before the Winter Came</td>\n",
       "      <td>Starred</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>44</th>\n",
       "      <td>1204</td>\n",
       "      <td>622</td>\n",
       "      <td>Elvis Costello</td>\n",
       "      <td>I Hear A Melody - Demo</td>\n",
       "      <td>Starred</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>45</th>\n",
       "      <td>1204</td>\n",
       "      <td>622</td>\n",
       "      <td>Elvis Costello</td>\n",
       "      <td>I Hope You're Happy Now - Live</td>\n",
       "      <td>Starred</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>46</th>\n",
       "      <td>1204</td>\n",
       "      <td>622</td>\n",
       "      <td>Elvis Costello</td>\n",
       "      <td>I Still Have That Other Girl</td>\n",
       "      <td>Starred</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>47</th>\n",
       "      <td>1204</td>\n",
       "      <td>622</td>\n",
       "      <td>Elvis Costello</td>\n",
       "      <td>I Want You - Live</td>\n",
       "      <td>Starred</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>48</th>\n",
       "      <td>1204</td>\n",
       "      <td>622</td>\n",
       "      <td>Elvis Costello</td>\n",
       "      <td>I'm Not Angry</td>\n",
       "      <td>Starred</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>49</th>\n",
       "      <td>1204</td>\n",
       "      <td>622</td>\n",
       "      <td>Elvis Costello</td>\n",
       "      <td>In The Darkest Place</td>\n",
       "      <td>Starred</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "    user_id  artist_id          artist  \\\n",
       "0       785        622  Elvis Costello   \n",
       "1       785        622  Elvis Costello   \n",
       "2       785        622  Elvis Costello   \n",
       "3       993        622  Elvis Costello   \n",
       "4       487        622  Elvis Costello   \n",
       "5       300        622  Elvis Costello   \n",
       "6       155        622  Elvis Costello   \n",
       "7       155        622  Elvis Costello   \n",
       "8       317        622  Elvis Costello   \n",
       "9       697        622  Elvis Costello   \n",
       "10      991        622  Elvis Costello   \n",
       "11      469        622  Elvis Costello   \n",
       "12      694        622  Elvis Costello   \n",
       "13      435        622  Elvis Costello   \n",
       "14     1184        622  Elvis Costello   \n",
       "15     1184        622  Elvis Costello   \n",
       "16     1204        622  Elvis Costello   \n",
       "17     1204        622  Elvis Costello   \n",
       "18     1204        622  Elvis Costello   \n",
       "19     1204        622  Elvis Costello   \n",
       "20     1204        622  Elvis Costello   \n",
       "21     1204        622  Elvis Costello   \n",
       "22     1204        622  Elvis Costello   \n",
       "23     1204        622  Elvis Costello   \n",
       "24     1204        622  Elvis Costello   \n",
       "25     1204        622  Elvis Costello   \n",
       "26     1204        622  Elvis Costello   \n",
       "27     1204        622  Elvis Costello   \n",
       "28     1204        622  Elvis Costello   \n",
       "29     1204        622  Elvis Costello   \n",
       "30     1204        622  Elvis Costello   \n",
       "31     1204        622  Elvis Costello   \n",
       "32     1204        622  Elvis Costello   \n",
       "33     1204        622  Elvis Costello   \n",
       "34     1204        622  Elvis Costello   \n",
       "35     1204        622  Elvis Costello   \n",
       "36     1204        622  Elvis Costello   \n",
       "37     1204        622  Elvis Costello   \n",
       "38     1204        622  Elvis Costello   \n",
       "39     1204        622  Elvis Costello   \n",
       "40     1204        622  Elvis Costello   \n",
       "41     1204        622  Elvis Costello   \n",
       "42     1204        622  Elvis Costello   \n",
       "43     1204        622  Elvis Costello   \n",
       "44     1204        622  Elvis Costello   \n",
       "45     1204        622  Elvis Costello   \n",
       "46     1204        622  Elvis Costello   \n",
       "47     1204        622  Elvis Costello   \n",
       "48     1204        622  Elvis Costello   \n",
       "49     1204        622  Elvis Costello   \n",
       "\n",
       "                                                track  \\\n",
       "0                (The Angels Wanna Wear My) Red Shoes   \n",
       "1                                              Alison   \n",
       "2                                 Tramp The Dirt Down   \n",
       "3                       Don't Let Me Be Misunderstood   \n",
       "4                                              Alison   \n",
       "5                                           Beautiful   \n",
       "6            Watching The Detectives - Single Version   \n",
       "7                                                 She   \n",
       "8                           Deep Dark Truthful Mirror   \n",
       "9                                              Alison   \n",
       "10                                           Veronica   \n",
       "11                                                She   \n",
       "12                   Waiting For The End Of The World   \n",
       "13                                             Alison   \n",
       "14                                             Alison   \n",
       "15           Watching The Detectives - Single Version   \n",
       "16  (I Don't Want To Go To) Chelsea - 1977/Live At...   \n",
       "17               (The Angels Wanna Wear My) Red Shoes   \n",
       "18  (The Angels Wanna Wear My) Red Shoes - 1977/Li...   \n",
       "19        (The Angels Wanna Wear My) Red Shoes - demo   \n",
       "20  (What's So Funny 'Bout) Peace, Love And Unders...   \n",
       "21                                             Alison   \n",
       "22          Alison - 1977/Live At The Nashville Rooms   \n",
       "23  Alison - 1977/Live At The Nashville Rooms - So...   \n",
       "24                                All Grown Up - Live   \n",
       "25                                   Blame It On Cain   \n",
       "26  Blame It On Cain - 1977/Live At The Nashville ...   \n",
       "27                                 Blue Minute - Demo   \n",
       "28                                  Brilliant Mistake   \n",
       "29                                  Call On Me - Demo   \n",
       "30                                  Changing Partners   \n",
       "31                                Complicated Shadows   \n",
       "32  Crawling To The USA - 1977/Live At The Nashvil...   \n",
       "33                   Down Among the Wines and Spirits   \n",
       "34                   Everyday I Write The Book - Live   \n",
       "35                               God Give Me Strength   \n",
       "36                        God Give Me Strength - Live   \n",
       "37                           Heart Of The City - Live   \n",
       "38                                       Hidden Shame   \n",
       "39  Hoover Factory - 1977/Live At The Nashville Rooms   \n",
       "40                                How Deep Is the Red   \n",
       "41                     I Don't Want To Go Home - Demo   \n",
       "42                          I Dreamed of My Old Lover   \n",
       "43            I Felt the Chill Before the Winter Came   \n",
       "44                             I Hear A Melody - Demo   \n",
       "45                     I Hope You're Happy Now - Live   \n",
       "46                       I Still Have That Other Girl   \n",
       "47                                  I Want You - Live   \n",
       "48                                      I'm Not Angry   \n",
       "49                               In The Darkest Place   \n",
       "\n",
       "                                             playlist  \n",
       "0                                      HARD ROCK 2010  \n",
       "1                                      HARD ROCK 2010  \n",
       "2                                      HARD ROCK 2010  \n",
       "3                                  Everything at once  \n",
       "4                                                Home  \n",
       "5                                             Ángeles  \n",
       "6                                         Vergaarbaak  \n",
       "7                                     Voorbeschouwing  \n",
       "8                 We Are Hunted Paramore Chart Jan 12  \n",
       "9                                All Time Great Songs  \n",
       "10                                               WBER  \n",
       "11  Las tipicas canciones que no sabes como se lla...  \n",
       "12                                            Starred  \n",
       "13                                   Liked from Radio  \n",
       "14                             best of radio paradise  \n",
       "15                             best of radio paradise  \n",
       "16                                            Starred  \n",
       "17                                            Starred  \n",
       "18                                            Starred  \n",
       "19                                            Starred  \n",
       "20                                            Starred  \n",
       "21                                            Starred  \n",
       "22                                            Starred  \n",
       "23                                            Starred  \n",
       "24                                            Starred  \n",
       "25                                            Starred  \n",
       "26                                            Starred  \n",
       "27                                            Starred  \n",
       "28                                            Starred  \n",
       "29                                            Starred  \n",
       "30                                            Starred  \n",
       "31                                            Starred  \n",
       "32                                            Starred  \n",
       "33                                            Starred  \n",
       "34                                            Starred  \n",
       "35                                            Starred  \n",
       "36                                            Starred  \n",
       "37                                            Starred  \n",
       "38                                            Starred  \n",
       "39                                            Starred  \n",
       "40                                            Starred  \n",
       "41                                            Starred  \n",
       "42                                            Starred  \n",
       "43                                            Starred  \n",
       "44                                            Starred  \n",
       "45                                            Starred  \n",
       "46                                            Starred  \n",
       "47                                            Starred  \n",
       "48                                            Starred  \n",
       "49                                            Starred  "
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "df_playlist = pd.read_csv('https://raw.githubusercontent.com/Gurobi/modeling-examples/master/music_recommendation/user_playlist_data.csv')\n",
    "print(df_playlist['user_id'].nunique(),\"users\") \n",
    "df_playlist.head(50) "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Train the prediction model using collaborative filtering\n",
    "\n",
    "We now train a prediction model to learn the users' preferences of artists using **collaborative filtering**, a popular method commonly used for product recommendations. The idea is to user the collaborative information of all other users to predict the preference of one user.\n",
    "\n",
    "To achieve collaborative filtering on this user-artist dataset, will use **matrix factorization**. Here, the key input is an $n \\times m$ matrix with rows comprising  $n$ users and columns comprising $m$ artists. An entry in the matrix denotes how much that user likes that artist- measured by the frequency of artist occurrence in their playlists. This matrix is factorized into two matrices with dimensions $n \\times d$ and $d \\times m$, where $d$ is the number of hidden features. The larger the number of features $d$, the more complex and nuanced the model is, albeit running the risk of over-fitting. See [here](https://towardsdatascience.com/intro-to-recommender-system-collaborative-filtering-64a238194a26) to read more about collaborative filtering.\n",
    "\n",
    "\n",
    "We will use [lightfm](https://github.com/lyst/lightfm) for matrix factorization as follows."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "import lightfm \n",
    "from lightfm import LightFM, cross_validation\n",
    "from lightfm.evaluation import auc_score  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Create the input matrix for matrix factorization using the frequency of occurrence of artists in each user's playlists."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>user_id</th>\n",
       "      <th>artist</th>\n",
       "      <th>freq</th>\n",
       "      <th>artist_id</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>701</td>\n",
       "      <td>The Rolling Stones</td>\n",
       "      <td>667</td>\n",
       "      <td>13</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>328</td>\n",
       "      <td>The Rolling Stones</td>\n",
       "      <td>658</td>\n",
       "      <td>13</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>351</td>\n",
       "      <td>The Rolling Stones</td>\n",
       "      <td>93</td>\n",
       "      <td>13</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>469</td>\n",
       "      <td>The Rolling Stones</td>\n",
       "      <td>77</td>\n",
       "      <td>13</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>603</td>\n",
       "      <td>The Rolling Stones</td>\n",
       "      <td>69</td>\n",
       "      <td>13</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "   user_id              artist  freq  artist_id\n",
       "0      701  The Rolling Stones   667         13\n",
       "1      328  The Rolling Stones   658         13\n",
       "2      351  The Rolling Stones    93         13\n",
       "3      469  The Rolling Stones    77         13\n",
       "4      603  The Rolling Stones    69         13"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Dataframe that stores the frequency of artists as they appear in users' playlists\n",
    "df_freq = df_playlist.groupby(['user_id', 'artist']).agg('size').reset_index().rename(columns={0:'freq'})[['user_id', 'artist', 'freq']].sort_values(['freq'], ascending=False)\n",
    "df_freq = df_freq.merge(df_artist[['artist_id','artist']], left_on='artist', right_on='artist') \n",
    "\n",
    "# Input matrix \n",
    "input_matrix = df_freq.groupby(['user_id', 'artist_id'])['freq'].sum().unstack().reset_index().fillna(0).set_index('user_id')\n",
    "\n",
    "# Dictionary to track the artist id for each artist name\n",
    "artists_dict ={(df_artist.loc[i,'artist_id']): df_artist.loc[i,'artist'] for i in range(df_artist.shape[0])} \n",
    "df_freq.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Before training the prediction model, the dataset is randomly split into a training set (80%) and a testing set (20%) to evaluate the fit. The quality of the fit is measured by the *area under an ROC curve (AUC)* metric. This is a measure of the likelihood that a preferred item by a user is ranked higher than a random item. The closer it is to 1, the better the fit. See [here](https://flowthytensor.medium.com/some-metrics-to-evaluate-recommendation-systems-9e0cf0c8b6cf) to read more about the AUC score.\n",
    "\n",
    "The parameters for the model are the number of components (or features), the loss function, number of epochs, among others. The values set for these parameters are shown below, but try other values to observe their effect on the performance. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Train AUC: 0.984152, test AUC: 0.985815\n"
     ]
    }
   ],
   "source": [
    "### Train-Test split\n",
    "from scipy import sparse\n",
    "x = sparse.csr_matrix(input_matrix.values)  \n",
    "train, test = lightfm.cross_validation.random_train_test_split(x, test_percentage=0.2, random_state=None)\n",
    "\n",
    "### Train the Matrix Factorization Model  \n",
    "model = LightFM(no_components=50, loss='warp')\n",
    "model.fit(x, epochs=30, num_threads = 4)\n",
    " \n",
    "train_auc = auc_score(model, train, num_threads=4).mean()  \n",
    "test_auc = auc_score(model, test, train_interactions=train, num_threads=4).mean()\n",
    "print('Train AUC: %f, test AUC: %f'%(train_auc,test_auc))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Select a user\n",
    "\n",
    "The prediction model has been trained. For the rest of the notebook, we focus on recommending artists for one user. First, select a user and retrieve their learned preference scores (a value between 0 and 1) among the artists. The higher the preference score, the greater their preference for that user according to the trained model.\n",
    "\n",
    "To select a user, use the dropdown at the bottom of this cell to choose a user id from 0 (default) through 1,305."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Select a user id from 0 through 1305.\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "031598b67f4e4a83a0fcd57db48060e2",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "interactive(children=(Dropdown(description='x', options=(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,…"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>artist_id</th>\n",
       "      <th>artist</th>\n",
       "      <th>country</th>\n",
       "      <th>listeners</th>\n",
       "      <th>preference</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>78</th>\n",
       "      <td>78</td>\n",
       "      <td>P!nk</td>\n",
       "      <td>United States</td>\n",
       "      <td>2495211</td>\n",
       "      <td>1.000000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>3</td>\n",
       "      <td>Rihanna</td>\n",
       "      <td>United States</td>\n",
       "      <td>4558193</td>\n",
       "      <td>0.983621</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>52</th>\n",
       "      <td>52</td>\n",
       "      <td>Christina Aguilera</td>\n",
       "      <td>United States</td>\n",
       "      <td>2788515</td>\n",
       "      <td>0.963143</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>66</th>\n",
       "      <td>66</td>\n",
       "      <td>Avril Lavigne</td>\n",
       "      <td>Canada</td>\n",
       "      <td>2627363</td>\n",
       "      <td>0.960885</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>90</th>\n",
       "      <td>90</td>\n",
       "      <td>Kelly Clarkson</td>\n",
       "      <td>United States</td>\n",
       "      <td>2425837</td>\n",
       "      <td>0.959522</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>30</th>\n",
       "      <td>30</td>\n",
       "      <td>Britney Spears</td>\n",
       "      <td>United States</td>\n",
       "      <td>3255085</td>\n",
       "      <td>0.921503</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>62</th>\n",
       "      <td>62</td>\n",
       "      <td>Usher</td>\n",
       "      <td>United States</td>\n",
       "      <td>2650020</td>\n",
       "      <td>0.895425</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>269</th>\n",
       "      <td>269</td>\n",
       "      <td>Whitney Houston</td>\n",
       "      <td>United States</td>\n",
       "      <td>1607022</td>\n",
       "      <td>0.895255</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>28</th>\n",
       "      <td>28</td>\n",
       "      <td>The Black Eyed Peas</td>\n",
       "      <td>United States</td>\n",
       "      <td>3346410</td>\n",
       "      <td>0.885004</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>16</th>\n",
       "      <td>16</td>\n",
       "      <td>Katy Perry</td>\n",
       "      <td>United States</td>\n",
       "      <td>3733134</td>\n",
       "      <td>0.882295</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>322</th>\n",
       "      <td>322</td>\n",
       "      <td>R. Kelly</td>\n",
       "      <td>United States</td>\n",
       "      <td>1456472</td>\n",
       "      <td>0.881024</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>109</th>\n",
       "      <td>109</td>\n",
       "      <td>Alicia Keys</td>\n",
       "      <td>United States</td>\n",
       "      <td>2325048</td>\n",
       "      <td>0.876612</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>20</th>\n",
       "      <td>20</td>\n",
       "      <td>Beyoncé</td>\n",
       "      <td>United States</td>\n",
       "      <td>3554615</td>\n",
       "      <td>0.872580</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>160</th>\n",
       "      <td>160</td>\n",
       "      <td>The Pussycat Dolls</td>\n",
       "      <td>United States</td>\n",
       "      <td>2045355</td>\n",
       "      <td>0.868011</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>176</th>\n",
       "      <td>176</td>\n",
       "      <td>Kylie Minogue</td>\n",
       "      <td>Australia</td>\n",
       "      <td>1951310</td>\n",
       "      <td>0.864342</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>22</th>\n",
       "      <td>22</td>\n",
       "      <td>Maroon 5</td>\n",
       "      <td>United States</td>\n",
       "      <td>3507447</td>\n",
       "      <td>0.863380</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>244</th>\n",
       "      <td>244</td>\n",
       "      <td>Leona Lewis</td>\n",
       "      <td>United Kingdom</td>\n",
       "      <td>1669852</td>\n",
       "      <td>0.858004</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>201</th>\n",
       "      <td>201</td>\n",
       "      <td>Train</td>\n",
       "      <td>United States</td>\n",
       "      <td>1834619</td>\n",
       "      <td>0.854389</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1012</th>\n",
       "      <td>1012</td>\n",
       "      <td>Luther Vandross</td>\n",
       "      <td>United States</td>\n",
       "      <td>700090</td>\n",
       "      <td>0.850716</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>149</th>\n",
       "      <td>149</td>\n",
       "      <td>Jennifer Lopez</td>\n",
       "      <td>United States</td>\n",
       "      <td>2099109</td>\n",
       "      <td>0.847628</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>360</th>\n",
       "      <td>360</td>\n",
       "      <td>Céline Dion</td>\n",
       "      <td>Canada</td>\n",
       "      <td>1376413</td>\n",
       "      <td>0.844627</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>12</th>\n",
       "      <td>12</td>\n",
       "      <td>Lady Gaga</td>\n",
       "      <td>United States</td>\n",
       "      <td>3820581</td>\n",
       "      <td>0.843301</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>694</th>\n",
       "      <td>694</td>\n",
       "      <td>Jordin Sparks</td>\n",
       "      <td>United States</td>\n",
       "      <td>902941</td>\n",
       "      <td>0.842717</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>144</th>\n",
       "      <td>144</td>\n",
       "      <td>Flo Rida</td>\n",
       "      <td>United States</td>\n",
       "      <td>2135943</td>\n",
       "      <td>0.840119</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>187</th>\n",
       "      <td>187</td>\n",
       "      <td>Nelly</td>\n",
       "      <td>United States</td>\n",
       "      <td>1907134</td>\n",
       "      <td>0.839648</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>33</th>\n",
       "      <td>33</td>\n",
       "      <td>Madonna</td>\n",
       "      <td>United States</td>\n",
       "      <td>3102727</td>\n",
       "      <td>0.837891</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>242</th>\n",
       "      <td>242</td>\n",
       "      <td>The Script</td>\n",
       "      <td>United Kingdom</td>\n",
       "      <td>1670620</td>\n",
       "      <td>0.837697</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>969</th>\n",
       "      <td>969</td>\n",
       "      <td>Girls Aloud</td>\n",
       "      <td>United Kingdom</td>\n",
       "      <td>723845</td>\n",
       "      <td>0.835629</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>21</th>\n",
       "      <td>21</td>\n",
       "      <td>Michael Jackson</td>\n",
       "      <td>United States</td>\n",
       "      <td>3546966</td>\n",
       "      <td>0.829854</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>342</th>\n",
       "      <td>342</td>\n",
       "      <td>Backstreet Boys</td>\n",
       "      <td>United States</td>\n",
       "      <td>1413894</td>\n",
       "      <td>0.826034</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "      artist_id               artist         country  listeners  preference\n",
       "78           78                 P!nk   United States    2495211    1.000000\n",
       "3             3              Rihanna   United States    4558193    0.983621\n",
       "52           52   Christina Aguilera   United States    2788515    0.963143\n",
       "66           66        Avril Lavigne          Canada    2627363    0.960885\n",
       "90           90       Kelly Clarkson   United States    2425837    0.959522\n",
       "30           30       Britney Spears   United States    3255085    0.921503\n",
       "62           62                Usher   United States    2650020    0.895425\n",
       "269         269      Whitney Houston   United States    1607022    0.895255\n",
       "28           28  The Black Eyed Peas   United States    3346410    0.885004\n",
       "16           16           Katy Perry   United States    3733134    0.882295\n",
       "322         322             R. Kelly   United States    1456472    0.881024\n",
       "109         109          Alicia Keys   United States    2325048    0.876612\n",
       "20           20              Beyoncé   United States    3554615    0.872580\n",
       "160         160   The Pussycat Dolls   United States    2045355    0.868011\n",
       "176         176        Kylie Minogue       Australia    1951310    0.864342\n",
       "22           22             Maroon 5   United States    3507447    0.863380\n",
       "244         244          Leona Lewis  United Kingdom    1669852    0.858004\n",
       "201         201                Train   United States    1834619    0.854389\n",
       "1012       1012      Luther Vandross   United States     700090    0.850716\n",
       "149         149       Jennifer Lopez   United States    2099109    0.847628\n",
       "360         360          Céline Dion          Canada    1376413    0.844627\n",
       "12           12            Lady Gaga   United States    3820581    0.843301\n",
       "694         694        Jordin Sparks   United States     902941    0.842717\n",
       "144         144             Flo Rida   United States    2135943    0.840119\n",
       "187         187                Nelly   United States    1907134    0.839648\n",
       "33           33              Madonna   United States    3102727    0.837891\n",
       "242         242           The Script  United Kingdom    1670620    0.837697\n",
       "969         969          Girls Aloud  United Kingdom     723845    0.835629\n",
       "21           21      Michael Jackson   United States    3546966    0.829854\n",
       "342         342      Backstreet Boys   United States    1413894    0.826034"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from __future__ import print_function\n",
    "import ipywidgets as widgets\n",
    "from ipywidgets import interact, interactive, fixed, interact_manual\n",
    "  \n",
    "def get_user_preference(x):  \n",
    "    # Create a dictionary that stores the preference for each artist as a number between 0 and 1\n",
    "    global preference, df_pref\n",
    "    preference = pd.Series(model.predict(x, np.arange(input_matrix.shape[1]))).to_dict()\n",
    "    lower_score = min(preference.values())\n",
    "    highest_score = max(preference.values())\n",
    "    preference = {artists_dict[i]:(preference[i]-lower_score)/(highest_score-lower_score) for i in preference}  \n",
    "     \n",
    "    # Print the known likes of the user\n",
    "    known_items = list(pd.Series(input_matrix.loc[x,:][input_matrix.loc[x,:] > 0].index).sort_values(ascending=False))\n",
    "    known_items = list(pd.Series(known_items).apply(lambda x: artists_dict[x])) \n",
    "    print(\"Top 20 artists this user already likes:\\n\",known_items[:20]) \n",
    "     \n",
    "    # Print the predicted preference scores of the user\n",
    "    df_pref = pd.DataFrame.from_records([(k, v) for k, v in preference.items()], columns =['artist', 'preference']) \n",
    "    df_pref = df_artist.merge(df_pref,left_on='artist',right_on='artist').sort_values(by = 'preference',ascending=False)\n",
    "    print(\"\\nPredicted preferences:\") \n",
    "\n",
    "n_users = df_playlist['user_id'].nunique()\n",
    "print(\"Select a user id from 0 through %i.\"%(n_users-1))\n",
    "interact(get_user_preference, x=list(range(n_users)))\n",
    "df_pref.head(30)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "readonly": false
   },
   "source": [
    "For our chosen user, we have the predicted preference scores for the artists. Looking at the most preferred artists, several of the artists are massively popular. For example, for user 0, their 30 most preferred artists have more than a million listeners on average. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The top 30 artists have 2374392 listeners on average.\n"
     ]
    }
   ],
   "source": [
    "print(\"The top 30 artists have %i listeners on average.\"% int(df_pref[['listeners']].head(30).sum()/30))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "readonly": false
   },
   "source": [
    "##  Optimize for popularity diversity\n",
    "\n",
    "\n",
    "But what if we want to recommend less popular artists? Adding niche artists to the basket of recommended artists could improve the **novelty** of the recommendation, while also supporting newer artists.\n",
    "\n",
    "We now introduce a mathematical optimization model to optimally resolve this trade-off between preference and popularity. \n",
    "An optimization model seeks to find the **best solution** according to an **objective function** such that the solution satisfies a pre-defined set of **constraints**. \n",
    "Here, a solution is expressed as a vector of real values or integer (e.g., binary) values called **decision variables**.\n",
    "Constraints are a set of equations or inequalities written as a function of the decision variables.\n",
    " \n",
    "For our model, a solution is given by the chosen set of artists. The objective function is to maximize the total preference scores among the chosen artists so that the recommended artists are likable. In order to control the overall popularity of the chosen artists, we enforce a constraint that the average popularity is less than a pre-defined maximum limit. We also add a constraint that the number of artists are fixed to a pre-defined value.\n",
    "Hence, this is an optimization model that maximizes preference with constraints on popularity and size.\n",
    "\n",
    "\n",
    "Let us now define some input parameters and notations used for creating the model. The subscript $a$ will be used to denote individual artists across the set of all artists we can choose from. The set of artists. preference and popularity are derived from the datasets. \n",
    "\n",
    "\n",
    "### Input parameters\n",
    "\n",
    "\n",
    "$p_a$: the user's preference for an artist $a$,\n",
    "\n",
    "$q_a$: popularity of artist $a$ measured by their number of listeners,\n",
    "\n",
    "$N$: number of artists to choose,\n",
    "  \n",
    "$P_{max}$: maximum limit on the average popularity among chosen set of artists.\n",
    "\n",
    "The following code loads the Gurobi python package and initiates the optimization model. The values of $N$ and $P_{max}$ can be altered in the below cell. For illustration, the maximum popularity limit is set to $500,000$ listeners, and the number of artists is set to $30$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Restricted license - for non-production use only - expires 2023-10-25\n"
     ]
    }
   ],
   "source": [
    "import gurobipy as gp \n",
    "from gurobipy import GRB\n",
    "\n",
    "artists = sorted(preference.keys(),reverse=True,key=lambda x : preference[x])[:1000]  # Set of the most preferred 1000 artists\n",
    "popularity = df_artist.groupby('artist')['listeners'].apply(float).to_dict() # Popularity of each artist\n",
    "P_max = 500000\n",
    "N = 30\n",
    "\n",
    "m = gp.Model(\"Recommend artists\") # this defines the model that we'll add to as we finish the formulation"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "readonly": false
   },
   "source": [
    "### Decision variables\n",
    "\n",
    "The key data structure that stores the solution of an optimization problem is a *decision variable*. In our model, we want to store whether or not an artist is chosen to be included in the solution. Hence, we define the following *binary decision variable* for each artist $a$ to represent a **yes** or **no** decision we want to make. This is modeled as $x_a = 1$  if artist $a$  is chosen; and $x_a = 0$  otherwise.\n",
    "\n",
    "\n",
    "Dictionaries are a good way to store the decision variables. We can add the variables either by looping through each artist, or all at once (as shown in the commented code below)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "x = {}\n",
    "for a in artists:\n",
    "    x[a] = m.addVar(vtype=GRB.BINARY)  \n",
    "# x = m.addVars(artists,vtype=GRB.BINARY)         "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "readonly": false
   },
   "source": [
    "### Objective\n",
    "\n",
    "Next, we will define the objective function: we want to maximize the total preference of the chosen artists.\n",
    "Using the binary decision variables, the objective can be written as follows.\n",
    "\n",
    "\\begin{align*}\n",
    "\\textrm{maximize} &\\sum_{a} p_a x_a\n",
    "\\end{align*}\n",
    "\n",
    "The following code adds the objective function to the model and tells the model that we want to maximize the function."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "m.setObjective(sum(x[a]*preference[a] for a in artists), GRB.MAXIMIZE)\n",
    "m.ModelSense = GRB.MAXIMIZE"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "readonly": false
   },
   "source": [
    "### Popularity constraint\n",
    "\n",
    "We want to make sure that the average popularity of the chosen artists is less than the pre-defined value $P_{max}$. This condition is expressed as a constraint written as follows.\n",
    " \n",
    "\\begin{align*}\n",
    "\\sum_{a} q_a x_a &\\leq P_{max}  \n",
    "\\end{align*}\n",
    "\n",
    "The following code adds this constraint to the model."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "popularity_constraints = m.addConstr(sum(x[a]*popularity[a] for a in artists) <= P_max*N)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "readonly": false
   },
   "source": [
    "### Size constraint\n",
    "\n",
    "Finally, we want to select exactly $N$ number of artists. This condition is expressed as the following constraint.\n",
    "\n",
    "\\begin{align*}\n",
    "\\sum_{a} x_a &= N \n",
    "\\end{align*}\n",
    "\n",
    "The following code adds this constraint to the model."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [],
   "source": [
    "n_artists_constraints = m.addConstr(sum(x[a] for a in artists) == N)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Finally, once all the constraints are added, we can tell Gurobi to solve the optimization model and find the optimal solution."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Gurobi Optimizer version 9.5.1 build v9.5.1rc2 (mac64[rosetta2])\n",
      "Thread count: 8 physical cores, 8 logical processors, using up to 8 threads\n",
      "Optimize a model with 2 rows, 1000 columns and 2000 nonzeros\n",
      "Model fingerprint: 0x5ae73861\n",
      "Variable types: 0 continuous, 1000 integer (1000 binary)\n",
      "Coefficient statistics:\n",
      "  Matrix range     [1e+00, 5e+06]\n",
      "  Objective range  [6e-01, 1e+00]\n",
      "  Bounds range     [1e+00, 1e+00]\n",
      "  RHS range        [3e+01, 2e+07]\n",
      "Presolve time: 0.01s\n",
      "Presolved: 2 rows, 1000 columns, 2000 nonzeros\n",
      "Variable types: 0 continuous, 1000 integer (1000 binary)\n",
      "Found heuristic solution: objective 20.9247336\n",
      "\n",
      "Root relaxation: objective 2.320614e+01, 6 iterations, 0.00 seconds (0.00 work units)\n",
      "\n",
      "    Nodes    |    Current Node    |     Objective Bounds      |     Work\n",
      " Expl Unexpl |  Obj  Depth IntInf | Incumbent    BestBd   Gap | It/Node Time\n",
      "\n",
      "     0     0   23.20614    0    2   20.92473   23.20614  10.9%     -    0s\n",
      "H    0     0                      23.1567009   23.20614  0.21%     -    0s\n",
      "H    0     0                      23.1716050   23.20614  0.15%     -    0s\n",
      "H    0     0                      23.1869771   23.20614  0.08%     -    0s\n",
      "     0     0   23.20074    0    3   23.18698   23.20074  0.06%     -    0s\n",
      "     0     0   23.20074    0    2   23.18698   23.20074  0.06%     -    0s\n",
      "     0     0   23.19759    0    2   23.18698   23.19759  0.05%     -    0s\n",
      "     0     0   23.19475    0    3   23.18698   23.19475  0.03%     -    0s\n",
      "     0     0   23.18919    0    3   23.18698   23.18919  0.01%     -    0s\n",
      "\n",
      "Cutting planes:\n",
      "  MIR: 1\n",
      "  RLT: 1\n",
      "\n",
      "Explored 1 nodes (17 simplex iterations) in 0.08 seconds (0.02 work units)\n",
      "Thread count was 8 (of 8 available processors)\n",
      "\n",
      "Solution count 5: 23.187 23.1836 23.1716 ... 20.9247\n",
      "\n",
      "Optimal solution found (tolerance 1.00e-04)\n",
      "Best objective 2.318697709962e+01, best bound 2.318919193748e+01, gap 0.0096%\n"
     ]
    }
   ],
   "source": [
    "m.optimize()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The optimizer solved the model. Let us now retrieve the optimal solution. The value of variable $x_a$ can be retrieved using the code \"x[a].X\"; artist $a$ is chosen if x[a].X is $1$.\n",
    "\n",
    "The following code stores the solution as a Pandas dataframe."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Artists chosen:\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>artists</th>\n",
       "      <th>popularity</th>\n",
       "      <th>preference</th>\n",
       "      <th>country</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>P!nk</td>\n",
       "      <td>2495211.0</td>\n",
       "      <td>1.000000</td>\n",
       "      <td>United States</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>R. Kelly</td>\n",
       "      <td>1456472.0</td>\n",
       "      <td>0.881024</td>\n",
       "      <td>United States</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>Luther Vandross</td>\n",
       "      <td>700090.0</td>\n",
       "      <td>0.850716</td>\n",
       "      <td>United States</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>Jordin Sparks</td>\n",
       "      <td>902941.0</td>\n",
       "      <td>0.842717</td>\n",
       "      <td>United States</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>Girls Aloud</td>\n",
       "      <td>723845.0</td>\n",
       "      <td>0.835629</td>\n",
       "      <td>United Kingdom</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>5</th>\n",
       "      <td>Toni Braxton</td>\n",
       "      <td>918419.0</td>\n",
       "      <td>0.822874</td>\n",
       "      <td>United States</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>6</th>\n",
       "      <td>Brandy</td>\n",
       "      <td>859521.0</td>\n",
       "      <td>0.814069</td>\n",
       "      <td>United States</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>7</th>\n",
       "      <td>Savage Garden</td>\n",
       "      <td>916122.0</td>\n",
       "      <td>0.807891</td>\n",
       "      <td>Australia</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>8</th>\n",
       "      <td>Blue</td>\n",
       "      <td>437763.0</td>\n",
       "      <td>0.792209</td>\n",
       "      <td>United Kingdom</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>9</th>\n",
       "      <td>Shontelle</td>\n",
       "      <td>460620.0</td>\n",
       "      <td>0.785508</td>\n",
       "      <td>Barbados</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>10</th>\n",
       "      <td>Iyaz</td>\n",
       "      <td>704826.0</td>\n",
       "      <td>0.781260</td>\n",
       "      <td>British Virgin Islands</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>11</th>\n",
       "      <td>Cheryl</td>\n",
       "      <td>629140.0</td>\n",
       "      <td>0.776973</td>\n",
       "      <td>United Kingdom</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>12</th>\n",
       "      <td>Jessica Simpson</td>\n",
       "      <td>650076.0</td>\n",
       "      <td>0.776038</td>\n",
       "      <td>United States</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>13</th>\n",
       "      <td>McFly</td>\n",
       "      <td>528862.0</td>\n",
       "      <td>0.761718</td>\n",
       "      <td>United Kingdom</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>14</th>\n",
       "      <td>Taylor Dayne</td>\n",
       "      <td>309272.0</td>\n",
       "      <td>0.761013</td>\n",
       "      <td>United States</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>15</th>\n",
       "      <td>Deborah Cox</td>\n",
       "      <td>177902.0</td>\n",
       "      <td>0.757848</td>\n",
       "      <td>United States</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>16</th>\n",
       "      <td>En Vogue</td>\n",
       "      <td>426606.0</td>\n",
       "      <td>0.755970</td>\n",
       "      <td>United States</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>17</th>\n",
       "      <td>B*Witched</td>\n",
       "      <td>190233.0</td>\n",
       "      <td>0.754877</td>\n",
       "      <td>Ireland</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>18</th>\n",
       "      <td>Reece Mastin</td>\n",
       "      <td>16022.0</td>\n",
       "      <td>0.752193</td>\n",
       "      <td>United Kingdom</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>19</th>\n",
       "      <td>Dappy</td>\n",
       "      <td>51655.0</td>\n",
       "      <td>0.741711</td>\n",
       "      <td>United Kingdom</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>20</th>\n",
       "      <td>Il Divo</td>\n",
       "      <td>313744.0</td>\n",
       "      <td>0.736224</td>\n",
       "      <td>United Kingdom</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>21</th>\n",
       "      <td>Billy Paul</td>\n",
       "      <td>242107.0</td>\n",
       "      <td>0.735403</td>\n",
       "      <td>United States</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>22</th>\n",
       "      <td>Karmin</td>\n",
       "      <td>291742.0</td>\n",
       "      <td>0.724293</td>\n",
       "      <td>United States</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>23</th>\n",
       "      <td>Wilson Phillips</td>\n",
       "      <td>208541.0</td>\n",
       "      <td>0.721221</td>\n",
       "      <td>United States</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>24</th>\n",
       "      <td>Brownstone</td>\n",
       "      <td>117646.0</td>\n",
       "      <td>0.716401</td>\n",
       "      <td>United States</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>25</th>\n",
       "      <td>Samantha Mumba</td>\n",
       "      <td>158985.0</td>\n",
       "      <td>0.712174</td>\n",
       "      <td>Ireland</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>26</th>\n",
       "      <td>Sam Bailey</td>\n",
       "      <td>17173.0</td>\n",
       "      <td>0.707936</td>\n",
       "      <td>United Kingdom</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>27</th>\n",
       "      <td>Déjà Vu</td>\n",
       "      <td>32261.0</td>\n",
       "      <td>0.700946</td>\n",
       "      <td>France</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>28</th>\n",
       "      <td>Union J</td>\n",
       "      <td>40189.0</td>\n",
       "      <td>0.700551</td>\n",
       "      <td>United Kingdom</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>29</th>\n",
       "      <td>Monica</td>\n",
       "      <td>13457.0</td>\n",
       "      <td>0.679592</td>\n",
       "      <td>United States</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "            artists  popularity  preference                 country\n",
       "0              P!nk   2495211.0    1.000000           United States\n",
       "1          R. Kelly   1456472.0    0.881024           United States\n",
       "2   Luther Vandross    700090.0    0.850716           United States\n",
       "3     Jordin Sparks    902941.0    0.842717           United States\n",
       "4       Girls Aloud    723845.0    0.835629          United Kingdom\n",
       "5      Toni Braxton    918419.0    0.822874           United States\n",
       "6            Brandy    859521.0    0.814069           United States\n",
       "7     Savage Garden    916122.0    0.807891               Australia\n",
       "8              Blue    437763.0    0.792209          United Kingdom\n",
       "9         Shontelle    460620.0    0.785508                Barbados\n",
       "10             Iyaz    704826.0    0.781260  British Virgin Islands\n",
       "11           Cheryl    629140.0    0.776973          United Kingdom\n",
       "12  Jessica Simpson    650076.0    0.776038           United States\n",
       "13            McFly    528862.0    0.761718          United Kingdom\n",
       "14     Taylor Dayne    309272.0    0.761013           United States\n",
       "15      Deborah Cox    177902.0    0.757848           United States\n",
       "16         En Vogue    426606.0    0.755970           United States\n",
       "17        B*Witched    190233.0    0.754877                 Ireland\n",
       "18     Reece Mastin     16022.0    0.752193          United Kingdom\n",
       "19            Dappy     51655.0    0.741711          United Kingdom\n",
       "20          Il Divo    313744.0    0.736224          United Kingdom\n",
       "21       Billy Paul    242107.0    0.735403           United States\n",
       "22           Karmin    291742.0    0.724293           United States\n",
       "23  Wilson Phillips    208541.0    0.721221           United States\n",
       "24       Brownstone    117646.0    0.716401           United States\n",
       "25   Samantha Mumba    158985.0    0.712174                 Ireland\n",
       "26       Sam Bailey     17173.0    0.707936          United Kingdom\n",
       "27          Déjà Vu     32261.0    0.700946                  France\n",
       "28          Union J     40189.0    0.700551          United Kingdom\n",
       "29           Monica     13457.0    0.679592           United States"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "chosen_artists = [a for a in artists if x[a].X > .99]  \n",
    "dict_artist_country = dict(zip(df_artist.artist, df_artist.country))\n",
    "\n",
    "df_chosen_artists = pd.DataFrame({'artists':chosen_artists, 'popularity':[popularity[i] for i in chosen_artists], 'preference':[preference[i] for i in chosen_artists], 'country':[dict_artist_country[i] for i in chosen_artists]})\n",
    "print(\"Artists chosen:\"%(chosen_artists))\n",
    "df_chosen_artists"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can observe that the artists are overall more *niche* than just picking the top 30 preferred artists. While this recommendation is diverse in terms of popularity, the artists are predominantly from a set of few countries. Can international diversity be infused into the recommendation?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "readonly": false
   },
   "source": [
    "## Improving international diversity using constraints\n",
    "\n",
    "The answer is yes. We now look at adding new constraints that enforce that there should be a pre-determined number of countries represented by the chosen set of artists.  The following is a new input parameter that allows us to set the extent of international diversity.\n",
    "\n",
    "- $C_{min}$: Minimum number of countries to be represented\n",
    "\n",
    "Setting $C_{min}$ to be $0$ has no effect on the model, whereas setting it to be $N$ (the number of artists selected) would ensure that all the artists are from $N$ difference countries. The following inputs are first defined."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [],
   "source": [
    "artists_in_country = df_artist[df_artist['artist'].isin(artists)].groupby('country')['artist'].apply(list).to_dict() # the set of artists from each country\n",
    "countries = artists_in_country.keys() # The set of all countries\n",
    "C_min = 20 # A number between 0 and N"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "readonly": false
   },
   "source": [
    "### Decision variables\n",
    "\n",
    "To track whether a country is represented or not, the following binary decision variable is created for each country $c$. This is modeled as $y_c = 1$  if country $c$  is represented; and $y_c = 0$  otherwise. \n",
    "\n",
    "The variables are added to the model. Note that we are adding it to the same model that we used earlier."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [],
   "source": [
    "y = m.addVars(countries, vtype=GRB.BINARY) "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "readonly": false
   },
   "source": [
    "### Constraints relating $y$ and $x$ \n",
    "\n",
    "We want to make sure that $y_c$ takes the value $0$ if none of the artists from country $c$ are chosen. \n",
    "Hence, we set $y_c$ to be $0$ if the value of $\\sum_\\limits{\\textrm{artists from country $c$}} x_a$ is $0$. This condition can be expressed as the following constraint.\n",
    "\n",
    "\\begin{align*}\n",
    "y_c  &\\leq \\sum_{\\textrm{artists from country $c$}} x_a \\quad  \\textrm{for each country}\\  c\n",
    "\\end{align*}\n",
    "\n",
    "We can add these constraints to the model."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [],
   "source": [
    "country_defining_constraints = m.addConstrs((y[c] <= sum(x[a] for a in artists_in_country[c])) for c in countries)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "readonly": false
   },
   "source": [
    "### International representation constraint\n",
    "\n",
    "Finally, we want to make sure that $C_{min}$ countries are represented, which can be expressed as the following constraint.  \n",
    "\\begin{align*}\n",
    "\\sum_{c} y_c &\\geq C_{min}\n",
    "\\end{align*}\n",
    "\n",
    "We can add this constraint to the model and initiate the optimization."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Gurobi Optimizer version 9.5.1 build v9.5.1rc2 (mac64[rosetta2])\n",
      "Thread count: 8 physical cores, 8 logical processors, using up to 8 threads\n",
      "Optimize a model with 38 rows, 1035 columns and 3070 nonzeros\n",
      "Model fingerprint: 0x68609e1d\n",
      "Variable types: 0 continuous, 1035 integer (1035 binary)\n",
      "Coefficient statistics:\n",
      "  Matrix range     [1e+00, 5e+06]\n",
      "  Objective range  [6e-01, 1e+00]\n",
      "  Bounds range     [1e+00, 1e+00]\n",
      "  RHS range        [2e+01, 2e+07]\n",
      "\n",
      "MIP start from previous solve did not produce a new incumbent solution\n",
      "\n",
      "Presolve removed 14 rows and 14 columns\n",
      "Presolve time: 0.00s\n",
      "Presolved: 24 rows, 1021 columns, 3042 nonzeros\n",
      "Variable types: 0 continuous, 1021 integer (1021 binary)\n",
      "Found heuristic solution: objective 20.4713302\n",
      "\n",
      "Root relaxation: objective 2.246906e+01, 53 iterations, 0.00 seconds (0.00 work units)\n",
      "\n",
      "    Nodes    |    Current Node    |     Objective Bounds      |     Work\n",
      " Expl Unexpl |  Obj  Depth IntInf | Incumbent    BestBd   Gap | It/Node Time\n",
      "\n",
      "     0     0   22.46906    0    2   20.47133   22.46906  9.76%     -    0s\n",
      "H    0     0                      22.4528803   22.46906  0.07%     -    0s\n",
      "H    0     0                      22.4554365   22.46906  0.06%     -    0s\n",
      "     0     0   22.46727    0    4   22.45544   22.46727  0.05%     -    0s\n",
      "     0     0   22.46727    0    2   22.45544   22.46727  0.05%     -    0s\n",
      "     0     0   22.46715    0    4   22.45544   22.46715  0.05%     -    0s\n",
      "     0     0   22.46388    0    5   22.45544   22.46388  0.04%     -    0s\n",
      "     0     0   22.45990    0    6   22.45544   22.45990  0.02%     -    0s\n",
      "     0     0   22.45748    0    7   22.45544   22.45748  0.01%     -    0s\n",
      "\n",
      "Cutting planes:\n",
      "  Gomory: 1\n",
      "  StrongCG: 1\n",
      "  GUB cover: 1\n",
      "  RLT: 1\n",
      "\n",
      "Explored 1 nodes (69 simplex iterations) in 0.06 seconds (0.02 work units)\n",
      "Thread count was 8 (of 8 available processors)\n",
      "\n",
      "Solution count 4: 22.4554 22.4529 22.4385 20.4713 \n",
      "\n",
      "Optimal solution found (tolerance 1.00e-04)\n",
      "Best objective 2.245543647035e+01, best bound 2.245748291362e+01, gap 0.0091%\n"
     ]
    }
   ],
   "source": [
    "country_limit_constraints = m.addConstr(sum(y[c] for c in artists_in_country) >= C_min) \n",
    "m.optimize()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Visualize the solution."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "30 artists in 20 countries chosen.\n",
      "Represented countries: ['Australia', 'Barbados', 'Belgium', 'British Virgin Islands', 'Canada', 'Denmark', 'France', 'Germany', 'Ireland', 'Italy', 'Jamaica', 'Moldova', 'Norway', 'Philippines', 'Romania', 'South Korea', 'Spain', 'Sweden', 'United Kingdom', 'United States']\n"
     ]
    },
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>artists</th>\n",
       "      <th>popularity</th>\n",
       "      <th>preference</th>\n",
       "      <th>country</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>R. Kelly</td>\n",
       "      <td>1456472.0</td>\n",
       "      <td>0.881024</td>\n",
       "      <td>United States</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>Luther Vandross</td>\n",
       "      <td>700090.0</td>\n",
       "      <td>0.850716</td>\n",
       "      <td>United States</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>Céline Dion</td>\n",
       "      <td>1376413.0</td>\n",
       "      <td>0.844627</td>\n",
       "      <td>Canada</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>Jordin Sparks</td>\n",
       "      <td>902941.0</td>\n",
       "      <td>0.842717</td>\n",
       "      <td>United States</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>Girls Aloud</td>\n",
       "      <td>723845.0</td>\n",
       "      <td>0.835629</td>\n",
       "      <td>United Kingdom</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>5</th>\n",
       "      <td>Enrique Iglesias</td>\n",
       "      <td>1549855.0</td>\n",
       "      <td>0.823422</td>\n",
       "      <td>Spain</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>6</th>\n",
       "      <td>Brandy</td>\n",
       "      <td>859521.0</td>\n",
       "      <td>0.814069</td>\n",
       "      <td>United States</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>7</th>\n",
       "      <td>Savage Garden</td>\n",
       "      <td>916122.0</td>\n",
       "      <td>0.807891</td>\n",
       "      <td>Australia</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>8</th>\n",
       "      <td>Blue</td>\n",
       "      <td>437763.0</td>\n",
       "      <td>0.792209</td>\n",
       "      <td>United Kingdom</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>9</th>\n",
       "      <td>Shontelle</td>\n",
       "      <td>460620.0</td>\n",
       "      <td>0.785508</td>\n",
       "      <td>Barbados</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>10</th>\n",
       "      <td>Iyaz</td>\n",
       "      <td>704826.0</td>\n",
       "      <td>0.781260</td>\n",
       "      <td>British Virgin Islands</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>11</th>\n",
       "      <td>Taylor Dayne</td>\n",
       "      <td>309272.0</td>\n",
       "      <td>0.761013</td>\n",
       "      <td>United States</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>12</th>\n",
       "      <td>Deborah Cox</td>\n",
       "      <td>177902.0</td>\n",
       "      <td>0.757848</td>\n",
       "      <td>United States</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>13</th>\n",
       "      <td>B*Witched</td>\n",
       "      <td>190233.0</td>\n",
       "      <td>0.754877</td>\n",
       "      <td>Ireland</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>14</th>\n",
       "      <td>Reece Mastin</td>\n",
       "      <td>16022.0</td>\n",
       "      <td>0.752193</td>\n",
       "      <td>United Kingdom</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>15</th>\n",
       "      <td>Aqua</td>\n",
       "      <td>736391.0</td>\n",
       "      <td>0.746050</td>\n",
       "      <td>Denmark</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>16</th>\n",
       "      <td>Dappy</td>\n",
       "      <td>51655.0</td>\n",
       "      <td>0.741711</td>\n",
       "      <td>United Kingdom</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>17</th>\n",
       "      <td>Billy Paul</td>\n",
       "      <td>242107.0</td>\n",
       "      <td>0.735403</td>\n",
       "      <td>United States</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>18</th>\n",
       "      <td>Sam Bailey</td>\n",
       "      <td>17173.0</td>\n",
       "      <td>0.707936</td>\n",
       "      <td>United Kingdom</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>19</th>\n",
       "      <td>Déjà Vu</td>\n",
       "      <td>32261.0</td>\n",
       "      <td>0.700946</td>\n",
       "      <td>France</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>20</th>\n",
       "      <td>PSY</td>\n",
       "      <td>588381.0</td>\n",
       "      <td>0.693093</td>\n",
       "      <td>South Korea</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>21</th>\n",
       "      <td>Lou Bega</td>\n",
       "      <td>439747.0</td>\n",
       "      <td>0.692931</td>\n",
       "      <td>Germany</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>22</th>\n",
       "      <td>Ylvis</td>\n",
       "      <td>225455.0</td>\n",
       "      <td>0.691592</td>\n",
       "      <td>Norway</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>23</th>\n",
       "      <td>Lasgo</td>\n",
       "      <td>296815.0</td>\n",
       "      <td>0.683486</td>\n",
       "      <td>Belgium</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>24</th>\n",
       "      <td>Alexandra Stan</td>\n",
       "      <td>459988.0</td>\n",
       "      <td>0.677999</td>\n",
       "      <td>Romania</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>25</th>\n",
       "      <td>The Wannadies</td>\n",
       "      <td>213619.0</td>\n",
       "      <td>0.670745</td>\n",
       "      <td>Sweden</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>26</th>\n",
       "      <td>O-Zone</td>\n",
       "      <td>345787.0</td>\n",
       "      <td>0.663930</td>\n",
       "      <td>Moldova</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>27</th>\n",
       "      <td>Black Box</td>\n",
       "      <td>217362.0</td>\n",
       "      <td>0.661630</td>\n",
       "      <td>Italy</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>28</th>\n",
       "      <td>Chaka Demus &amp; Pliers</td>\n",
       "      <td>185724.0</td>\n",
       "      <td>0.657663</td>\n",
       "      <td>Jamaica</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>29</th>\n",
       "      <td>Lea Salonga</td>\n",
       "      <td>155708.0</td>\n",
       "      <td>0.645321</td>\n",
       "      <td>Philippines</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                 artists  popularity  preference                 country\n",
       "0               R. Kelly   1456472.0    0.881024           United States\n",
       "1        Luther Vandross    700090.0    0.850716           United States\n",
       "2            Céline Dion   1376413.0    0.844627                  Canada\n",
       "3          Jordin Sparks    902941.0    0.842717           United States\n",
       "4            Girls Aloud    723845.0    0.835629          United Kingdom\n",
       "5       Enrique Iglesias   1549855.0    0.823422                   Spain\n",
       "6                 Brandy    859521.0    0.814069           United States\n",
       "7          Savage Garden    916122.0    0.807891               Australia\n",
       "8                   Blue    437763.0    0.792209          United Kingdom\n",
       "9              Shontelle    460620.0    0.785508                Barbados\n",
       "10                  Iyaz    704826.0    0.781260  British Virgin Islands\n",
       "11          Taylor Dayne    309272.0    0.761013           United States\n",
       "12           Deborah Cox    177902.0    0.757848           United States\n",
       "13             B*Witched    190233.0    0.754877                 Ireland\n",
       "14          Reece Mastin     16022.0    0.752193          United Kingdom\n",
       "15                  Aqua    736391.0    0.746050                 Denmark\n",
       "16                 Dappy     51655.0    0.741711          United Kingdom\n",
       "17            Billy Paul    242107.0    0.735403           United States\n",
       "18            Sam Bailey     17173.0    0.707936          United Kingdom\n",
       "19               Déjà Vu     32261.0    0.700946                  France\n",
       "20                   PSY    588381.0    0.693093             South Korea\n",
       "21              Lou Bega    439747.0    0.692931                 Germany\n",
       "22                 Ylvis    225455.0    0.691592                  Norway\n",
       "23                 Lasgo    296815.0    0.683486                 Belgium\n",
       "24        Alexandra Stan    459988.0    0.677999                 Romania\n",
       "25         The Wannadies    213619.0    0.670745                  Sweden\n",
       "26                O-Zone    345787.0    0.663930                 Moldova\n",
       "27             Black Box    217362.0    0.661630                   Italy\n",
       "28  Chaka Demus & Pliers    185724.0    0.657663                 Jamaica\n",
       "29           Lea Salonga    155708.0    0.645321             Philippines"
      ]
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "chosen_artists = [a for a in artists if x[a].X > .99]\n",
    "chosen_countries = [c for c in artists_in_country if y[c].x > 0.99]\n",
    "print(\"%i artists in %i countries chosen.\"%(len(chosen_artists),len(chosen_countries)))\n",
    "print(\"Represented countries:\",chosen_countries)\n",
    "\n",
    "df_chosen_artists = pd.DataFrame({'artists':chosen_artists, 'popularity':[popularity[i] for i in chosen_artists], 'preference':[preference[i] for i in chosen_artists], 'country':[dict_artist_country[i] for i in chosen_artists]})\n",
    "sns.scatterplot(data=df_chosen_artists, x=\"preference\", y=\"popularity\", hue=\"country\", legend = False)\n",
    "plt.xlim(0, 1)\n",
    "plt.ylim(0, 500000)\n",
    "plt.show()\n",
    "df_chosen_artists "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can definitely see more international diversity among the chosen set of artists."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "While this notebook walked through how to build an optimization model piece-by-piece, the following code contains the overall optimization model. You can input different parameter values for $P_{max}$ (maximum limit on the average popularity), $N$ (number of chosen artists ) and $C_{min}$ (minimum limit on the number of countries to be represented) and see how the optimal solution changes. The value of $P_{max}$ can be controlled using the slider below the cell.\n",
    "\n",
    "What happens if $P_{max}$ is too low? Is there a trade-off between popularity and likeability? Can you find a solution where all the artists are uniquely different countries?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Select a maximum limit on the average popularity:\n",
      "\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "38ebfcbac7bd4f03b9d39e600cc4e8ff",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "interactive(children=(IntSlider(value=1000000, description='x', max=2000000, min=200000, step=200000), Output(…"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/plain": [
       "<function __main__.solve(x)>"
      ]
     },
     "execution_count": 26,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def solve(x):\n",
    "    # Input parameters for artist selection\n",
    "    P_max = x # maximumum limit on the average popularity\n",
    "    N = 30 # number of chosen artists \n",
    "    C_min = 30 # minimum limit on the number of countries to be represented\n",
    "    \n",
    "    #Initiate the model \n",
    "    m = gp.Model(\"Playlisting\")  \n",
    "    \n",
    "    # Define the variables\n",
    "    x = m.addVars(artists,vtype=GRB.BINARY) \n",
    "    y = m.addVars(countries, vtype=GRB.BINARY) \n",
    "    \n",
    "    # Define the objective \n",
    "    m.setObjective(sum(x[a]*preference[a] for a in artists), GRB.MAXIMIZE)\n",
    "    m.ModelSense = GRB.MAXIMIZE\n",
    "    \n",
    "    # Add the constraitns \n",
    "    m.addConstr(sum(x[a]*popularity[a] for a in artists) <= P_max*N) # Sets a maximum limit on the average popularity\n",
    "    m.addConstr(sum(x[a] for a in artists) == N) # Selects exactly N artists \n",
    "      \n",
    "    m.addConstrs((y[c] <= sum(x[a] for a in artists_in_country[c])) for c in countries) # Selects a country only if an artist from that country is chosen\n",
    "    m.addConstr(sum(y[c] for c in artists_in_country) >= C_min) # Sets a minimum limit on the number of countries represented\n",
    "    \n",
    "    m.setParam('OutputFlag', 0) # Suppress the output\n",
    "    m.optimize() # Run the optimization of the model\n",
    "    \n",
    "    if m.status != 2:\n",
    "        print(\"\\n The problem is infeasible. There is no solution that satisfies all the constraints.\")\n",
    "        print(\"Try increasing the maximum popularity limit, or decreasing the minimum number of countries to be represented.\")\n",
    "        return\n",
    " \n",
    "    chosen_artists = [a for a in artists if x[a].X > .99] # Chosen set of artists \n",
    "    chosen_countries = [c for c in artists_in_country if y[c].x > 0.99] # Chosen set of countries \n",
    "    print(\"%i artists in %i countries chosen.\"%(len(chosen_artists),len(chosen_countries)))\n",
    "    print(\"Represented countries:\",chosen_countries)\n",
    "\n",
    "    # Create a dataframe for the visualization\n",
    "    df_chosen_artists = pd.DataFrame({'artists':chosen_artists, 'popularity':[popularity[i] for i in chosen_artists], 'preference':[preference[i] for i in chosen_artists], 'country':[dict_artist_country[i] for i in chosen_artists]})\n",
    "    sns.scatterplot(data=df_chosen_artists, x=\"preference\", y=\"popularity\", hue=\"country\", legend = False)\n",
    "    plt.xlim(0.6, 1)\n",
    "    plt.ylim(0, 5000000)\n",
    "    plt.show()\n",
    "    print(df_chosen_artists)\n",
    "     \n",
    "print(\"Select a maximum limit on the average popularity:\\n\")\n",
    "interact(solve, x=(200000,2000000,200000))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Copyright © 2022 Gurobi Optimization, LLC"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.7"
  },
  "vscode": {
   "interpreter": {
    "hash": "f30ef14de2ae67aa36b75dc3c292f6e4444544151dcaad5793df1fef440bd000"
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
